/*
 * conexiones.c
 *
 *  Created on: 22/11/2011
 *      Author: MagayMade
 */

#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <semaphore.h>
#include <time.h>
#include "conexiones.h"
#include "listaEntries.h"
#include "lista.h"
#include "bootsecto.h"

extern int lecturaEnProceso;
extern t_conexion *conexiones;
extern pthread_mutex_t semConexiones;
extern sem_t semSockContador;
extern bootSector bootSec;

int available_connection()
{
	int i=0;

	sem_wait(&semSockContador);
	pthread_mutex_lock(&semConexiones);
	while(conexiones[i].libre != FREE )
		i++;
	conexiones[i].libre = BUSY;
	pthread_mutex_unlock(&semConexiones);

	return i;
}

void armarPayload(nipcMessage msg, unsigned int sector, char *buffer)
{
	if(msg.type == WRITE) /* "NumeroSector;Buffer" */
	{
		  sprintf(msg.payload, "%d;%s", sector, buffer);
	}
	if (msg.type == READ) /* "NumeroSector;" */
	{
		  sprintf(msg.payload, "%d;",sector);
	}
}

struct t_cache *check_cache(struct TDD_entry *nodoPath, unsigned int sector)
{
	struct t_cache *aux, *ant;

	pthread_mutex_lock(&nodoPath->mutexCache);

	aux = nodoPath->cache;
	ant = NULL;

	while((aux != NULL) && (aux->sector < sector))
	{
		ant = aux;
		aux = aux->siguiente;
	}

	/* si el sector no esta */
	if(aux == NULL)
	{
		pthread_mutex_unlock(&nodoPath->mutexCache);
		return NULL;
	}
	else /* si el sector esta lo devuelvo */
	{
		pthread_mutex_unlock(&nodoPath->mutexCache);
		return aux;
	}
}

int insertarSecOrdenado(struct t_cache **nodoCache, struct t_cache *newSector){

	struct t_cache *aux, *ant;

	/* la cache esta vacia */
	if(*nodoCache == NULL)
	{
	   *nodoCache = newSector;
	   return 0;
	}
	else
	{
		/* si el sector a insertar es menor que el primero */
		if(newSector->sector < (*nodoCache)->sector)
		{
			newSector->siguiente = *nodoCache;
			*nodoCache = newSector;
			return 0;
		}

		aux = *nodoCache;
		ant = NULL;

		while((aux != NULL) && (aux->sector < newSector->sector))
		{
			ant = aux;
			aux = aux->siguiente;
		}

		/* si el nuevo nodo va al final */
		if(aux == NULL)
		{
			ant->siguiente = newSector;
		}
		else /* si el nuevo nodo va entre ant y aux */
		{
			ant->siguiente = newSector;
			newSector->siguiente = aux;
		}
	}

	return 0;
}

void sacarNodoSector(struct t_cache **nodoCache, struct t_cache *antSector)
{
	struct t_cache *aux;

	/* si es el primer sector de la cache */
	if(antSector == NULL)
	{
		aux = *nodoCache;
		*nodoCache = (*nodoCache)->siguiente;
	}
	else
	{
		aux = antSector->siguiente;
		antSector->siguiente = antSector->siguiente->siguiente;
	}

	/* depende de si le hago malloc al buffer en cargarCache
	 * free(aux->buffer); */
	free(aux);
}

int cargarCache(struct TDD_entry *nodoPath, char *buf, unsigned int sector)
{
	time_t 			*timeS = NULL, *min;
	int 			indice;
	struct t_cache 	*newSector, *aux, *ant, *secEliminar, *antEliminar;
	nipcMessage		msg;

	newSector = (struct t_cache*) malloc (sizeof(struct t_cache));

	/* si en t_cache, buffer esta definido de 512, tengo que hacerle malloc al buffer???
	 * newSector->buffer = (char*) malloc(512 + 1); */

	newSector->sector = sector;
	strcpy(newSector->buffer, buf);
	time(timeS);
	newSector->timeStand = timeS;
	newSector->modificado = NOMODIFICADO;
	newSector->siguiente = NULL;

	/* si tengo espacio en cache */
	if(nodoPath->cantSecEnCache > 0)
	{
		insertarSecOrdenado(&nodoPath->cache, newSector);
		nodoPath->cantSecEnCache--;
	}
	else
	{
		min = nodoPath->cache->timeStand;

		aux = nodoPath->cache;
		ant = NULL;
		secEliminar = NULL;
		antEliminar = NULL;

		while(aux != NULL)
		{
			if(aux->timeStand < min)
			{
				min = aux->timeStand;
				secEliminar = aux;
				antEliminar = ant;
			}

			ant = aux;
			aux = aux->siguiente;
		}
	}

	/* si el sector esta modificado en cache */
	if(secEliminar->modificado != 0)
	{
		indice = available_connection();
		memset(msg.payloadID, '\0', 6);
		sprintf(msg.payloadID ,"%d", conexiones[indice].socket);
		msg.type = WRITE;
		armarPayload(msg,sector, secEliminar->buffer);
		nipc_send(&msg, atoi(msg.payloadID));
		nipc_recv(&msg, atoi(msg.payloadID));
		pthread_mutex_lock(&semConexiones);
		conexiones[indice].libre = FREE;
		pthread_mutex_unlock(&semConexiones);
		sem_post(&semSockContador);
	}

	sacarNodoSector(&nodoPath->cache, antEliminar);

	insertarSecOrdenado(&nodoPath->cache, newSector);

	return 0;
}

void armarBuf(sec_arch *sectores, int tamanio, char *buf, int w, int w2, unsigned int bloque, int secPerBloque)
{
	int i, j;
	unsigned int sector;
	div_t division;
	char *buffer;

	if((w == bloque) || ( w == 0))
	{
		strcat(buf, sectores[0].buffer);
		free(sectores[0].buffer);
		sector = 1;
	}
	else
	{
		if(w < bloque)
			{
				division = div(w, 512);
				if(division.rem != 0)
				{
					sector = division.quot +1;
					strncpy(buf, &sectores[sector].buffer[division.rem], (512-division.rem));
					for(j=0; j<sector;j++)
					{
					free(sectores[sector].buffer);
					}
					sector+=1;
				}
				else
				{
					sector = division.quot;
					strcat(buf, sectores[0].buffer);
					free(sectores[sector].buffer);
					sector+=1;
				}
			}
		else
		{
			w = w - bloque;
			division = div(w, 512);
			if(division.rem != 0)
			{
				sector = division.quot +1;
				strncpy(buf, &sectores[sector].buffer[division.rem], (512-division.rem));
				for(j=0; j<sector;j++)
				{
				free(sectores[sector].buffer);
				}
				sector+=1;
			}
			else
			{
				sector = division.quot;
				strcat(buf, sectores[0].buffer);
				free(sectores[sector].buffer);
				sector+=1;
			}
		}
	}

	for(i=sector; i<(tamanio-secPerBloque); i++)
	{
		strcat(buf, sectores[i].buffer);
		free(sectores[i].buffer);
		j=i;
	}

	if( ((w2 - bloque) == 0) || (w2 == bloque))
	{
		for(i=(tamanio-secPerBloque); i<tamanio; i++)
		{
			strcat(buf, sectores[j].buffer);
			free(sectores[j].buffer);
			j++;
		}
	}
	else
	{
		if( w2 < bloque)
		{
			division = div(w2, 512);
			if(division.rem != 0)
			{
				sector = division.quot + 1;
				strncpy(buffer, sectores[sector].buffer, division.rem);
				strcat(buf, buffer);
				for(j=sector; j<tamanio;j++)
				{
				free(sectores[j].buffer);
				}
			}
		}
		else
		{
			w2 -= bloque;
			division = div(w2, 512);
			if(division.rem != 0)
			{
				sector = division.quot +1;
				strncpy(buffer, &(sectores[sector].buffer), division.rem);
				strcat(buf, buffer);
				for(j=sector; j<tamanio;j++)
				{
				free(sectores[j].buffer);
				}
			}
		}
	}
}

int leerSector(struct TDD_entry *nodoPath, unsigned int sector, char **buffer)
{
	struct t_cache	*nodoCache;
	time_t 			*timeS = NULL;
	nipcMessage 	msg;
	int 			indice;

	/* si tengo cache */
	if(nodoPath != NULL)
	{
		nodoCache = check_cache(nodoPath, sector);

		if(nodoCache != NULL)
		{
			lecturaEnProceso = 1;
			strcpy(*buffer, nodoCache->buffer);
			time(timeS);
			nodoCache->timeStand = timeS;
			lecturaEnProceso = 0;
		}else{
			indice = available_connection();
			sprintf(msg.payloadID ,"%d", conexiones[indice].socket);
			msg.type = READ;
			armarPayload(msg, sector, NULL);
			nipc_send(&msg, atoi(msg.payloadID));
			nipc_recv(&msg, atoi(msg.payloadID));
			pthread_mutex_lock(&semConexiones);
			conexiones[indice].libre = FREE;
			pthread_mutex_unlock(&semConexiones);
			sem_post(&semSockContador);
			strtok(msg.payload, ";");
			strcpy(*buffer, strtok(NULL, "\0"));
			pthread_mutex_lock(&nodoPath->mutexCache);
			cargarCache(nodoPath, *buffer, sector);
			pthread_mutex_unlock(&nodoPath->mutexCache);
		}
	}
	else
	{
		indice = available_connection();
		sprintf(msg.payloadID ,"%d", conexiones[indice].socket);
		msg.type = READ;
		armarPayload(msg, sector, NULL);
		nipc_send(&msg, atoi(msg.payloadID));
		nipc_recv(&msg, atoi(msg.payloadID));
		pthread_mutex_lock(&semConexiones);
		conexiones[indice].libre = FREE;
		pthread_mutex_unlock(&semConexiones);
		sem_post(&semSockContador);
		strtok(msg.payload, ";");
		strcpy(*buffer, strtok(NULL, "\0"));
	}

	return 0;
}

void crearListaDeSectores(unsigned int primerSector, int secPerBloque, unsigned int **lista)
{
	int i;

	*lista[0]= primerSector;

	for(i=1; i< secPerBloque; i++)
	{
		*lista[i] = primerSector + 1;
	}
}

void getBloque(struct TDD_entry *nodoPath, unsigned int sector, int bloque, int secPerBloque, sec_arch **sectores, int tamanio){

	int 			i;
	unsigned int 	*lista;

	lista = (unsigned int *) malloc(sizeof(unsigned int) * secPerBloque);
	*sectores = (sec_arch *) malloc(sizeof(sec_arch));

	/* creamos la lista de sectores a pedir segun el bloque solicitado */
	switch(bloque)
	{
	case 1:
		crearListaDeSectores(sector, secPerBloque, &lista);
		tamanio += secPerBloque;
		break;
	case 2:
		crearListaDeSectores(sector + secPerBloque, secPerBloque, &lista);
		tamanio += secPerBloque;
		break;
	default:
		break;
	}

	/* si hay cache busca ahi, sino pide al RAID/PPD */
	for(i = 0; i < secPerBloque; i++)
	{
		sectores[i]->sector = lista[i];
		sectores[i]->buffer = (char *) malloc(512);
		leerSector(nodoPath, sectores[i]->sector, &(sectores[i]->buffer));
	}

	free(lista);
}

int writeFile(struct TDD_entry **nodoDelPath, char *buf, size_t size, off_t offset, const char *path)
{
	div_t 					division;
	int 					secPerBloque, bytesEnCluster, tamanio = 0, res, bytesEscritos, pos = 0, i;
	int						primeraVez = 1, escribirMitadSector = 0;
	unsigned int 			primerByteModificar, byteModificarDentroDeSector, bytesPendientes;
	unsigned int 			sectorLibre, primerSecClusterModif, l, tamanioCluster, bloque;
	unsigned int			primerCluster, clusterModificar, clusterLibre;
	struct directoryEntry 	*entry;
	char					disk, *buffer, *bufferSectores, *buf2;
	struct Nodo				*freeClusters;
	bootSector				boot;
	sec_arch 				*sectores;

	tamanioCluster = bootSec.sectorPerCluster * 512;

	/* Calculo el tamaño de mi bloque */
	bloque = (tamanioCluster / 2);
	secPerBloque = bootSec.sectorPerCluster / 2;

	/* busco el cluster desde donde quiero escribir */
	division = div(offset, tamanioCluster);

	/* si el offset cae en la mitad de un cluster, le sumo uno a la parte entera
	 * si el offset cae justo al final del cluster, le sumo uno a la parte entera para pedir el siguiente*/
	primerCluster = division.quot + 1;

	/* posicion dentro del ultimo cluster desde donde quiero escribir */
	primerByteModificar =  offset - (division.quot * tamanioCluster);
	bytesEnCluster = tamanioCluster - primerByteModificar;

	res = read_entry(disk, path, rootclus , entry);

	boot = getBootSector(disk);

	/* Encontro el archivo */
	if( res == 1)
	{
		/* si no tiene espacio asignado */
		if( entry->fileSize == 0 )
		{
			/* pido todos los clusters que necesite para escribir */
			division = div(size, tamanioCluster);

			if (division.rem != 0)
			{
				division.quot ++;
			}

			while( division.quot > 0)
			{
				freeClusters = getFreeClusters(disk);
				clusterLibre = unqueue(&freeClusters);

				if(primeraVez == 1)
				{
					entry->firstCluster = clusterLibre;
					primeraVez = 0;
				}

				sectorLibre = getSectorClusterN(clusterLibre, boot);

				if (size > bloque)
				{
					bytesEscritos = setBloque(sectorLibre, 1, secPerBloque, &buf, nodoDelPath);
					bytesEscritos =+ setBloque(sectorLibre, 2 , secPerBloque, &buf, nodoDelPath);
				}
				else
				{
					bytesEscritos = setBloque(sectorLibre, 1, secPerBloque, &buf, nodoDelPath);
				}

				division.quot --;
			}

			entry->fileSize = bytesEscritos;
		}
		else
		{
			/* si modifico en la mitad de un cluster, SIEMPRE va a estar YA asignado al archivo */
			if(primerByteModificar != 0)
			{
				/* si es el primer cluster lo saco de la entry */
				if(primerCluster == 1)
				{
					clusterModificar = entry->firstCluster;
				}
				else
				{
					clusterModificar = clusterDeUnArchivo(disk, path, primerCluster - 1);
				}

				primerSecClusterModif = getSectorClusterN(clusterModificar, boot);

				/* sobreescribo el primer cluster (amen de si modifico uno o mas clusters) */
				division = div(primerByteModificar, 512);

				escribirMitadSector = division.rem;

				/* si tengo que escribir en el medio de un sector */
				if(escribirMitadSector != 0)
				{
					/* si empiezo a escribir en el primer bloque */
					if (primerByteModificar < bloque)
					{
						getBloque(*nodoDelPath, primerSecClusterModif, 1, secPerBloque, &sectores, tamanio);
					}
					else
					{
						getBloque(*nodoDelPath, primerSecClusterModif, 2, secPerBloque, &sectores, tamanio);
					}

					byteModificarDentroDeSector = primerByteModificar;

					for(l = primerByteModificar; l > 512; l =- 512)
					{
						pos++;
						byteModificarDentroDeSector =- 512;
					}

					completarSector(byteModificarDentroDeSector, &sectores[pos].buffer, buf);

					buffer = (char *) malloc(strlen(buf));
					strcpy(buffer, buf);
					strcpy(buffer, &buffer[byteModificarDentroDeSector]);

					bufferSectores = (char *) malloc(512 * secPerBloque);

					for(i = 0; i < pos + 1; i++)
					{
						strcat(bufferSectores, sectores[i].buffer);
					}

					buf2 = (char *) malloc(strlen(bufferSectores) + strlen(buffer));

					strcat(buf2, bufferSectores);
					strcat(buf2, buffer);

					free(bufferSectores);

					/* si empiezo a escribir en el primer bloque */
					if (primerByteModificar < bloque)
					{
						bytesEscritos = setBloque(primerSecClusterModif, 1 , secPerBloque, &buf2, nodoDelPath);
						bytesEscritos = setBloque(primerSecClusterModif, 2 , secPerBloque, &buf2, nodoDelPath);
					}
					else
					{
						bytesEscritos =+ setBloque(primerSecClusterModif, 2 , secPerBloque, &buf2, nodoDelPath);
					}

					bytesPendientes = strlen(buf2);

					free(buffer);
				}
				else /* si empiezo a escribir en el comienzo del bloque */
				{
					/* si empiezo a escribir en el primer bloque */
					if (primerByteModificar < bloque)
					{
						bytesEscritos = setBloque(primerSecClusterModif, 1 , secPerBloque, &buf, nodoDelPath);
					}
					else
					{
						bytesEscritos = setBloque(primerSecClusterModif, 1 , secPerBloque, &buf, nodoDelPath);
						bytesEscritos =+ setBloque(primerSecClusterModif, 2 , secPerBloque, &buf, nodoDelPath);
					}

					bytesPendientes = strlen(buf);
				}

				/* si queda info por guardar */
				if(bytesPendientes > 0)
				{
					/* si antes escribi en el medio de un sector (uso buf2) */
					if(escribirMitadSector != 0)
					{
						escribirClustersRestantes(nodoDelPath, buf2, tamanioCluster, (*nodoDelPath)->path, primerCluster, boot, secPerBloque);
					}
					else /* sino tengo que usar buf */
					{
						escribirClustersRestantes(nodoDelPath, buf, tamanioCluster, (*nodoDelPath)->path, primerCluster, boot, secPerBloque);
					}
				}
			}
			else /* si empiezo a escribir al comienzo de un cluster */
			{
				escribirClustersRestantes(nodoDelPath, buf, tamanioCluster, (*nodoDelPath)->path, primerCluster, boot, secPerBloque);
			}

			entry->fileSize =+ strlen(buf);
		}

		return bytesEscritos;
	}
	else /* No lo encontro, se devuelve que escribi 0 bytes */
	{
		return 0;
	}
}

unsigned int setBloque(unsigned int sector, int bloque, int secPerBloque, char **buf, struct TDD_entry **nodoDelPath)
{

	int 			i;
	unsigned int 	*lista, bytesEscritos;
	char 			*buffer;

	lista = (unsigned int *) malloc(sizeof(unsigned int) * secPerBloque);

	switch(bloque)
	{
		case 1:
			crearListaDeSectores(sector, secPerBloque, &lista);
			break;
		case 2:
			crearListaDeSectores(sector + secPerBloque, secPerBloque, &lista);
			break;
		default:
			break;
	}

	for(i = 0; i < secPerBloque; i++)
	{
		buffer = (char*) malloc(512);
		strncpy(buffer, *buf , 512);
		bytesEscritos = bytesEscritos + escribirSector(nodoDelPath, lista[i], buffer);
		strncpy(*buf , &(*buf[(i+1) * 512]), strlen(*buf) - 512 );
	}

	free(lista);
	return bytesEscritos;
}

char *getSector(unsigned int sector){

	nipcMessage msg;
	char 		*buffer;
	int		 	fd,indice;

	indice = available_connection();

	fd=conexiones[indice].socket;


	 char string[intlen(sector)];
	 sprintf(string,"%d",sector);
     strcat(string, ";");


	sprintf(msg.payloadID, "%d", fd);
	msg.type = '1';
	msg.payload = string;  // ESTE ES EL NRO DE SECTOR QUE QUIERO
	msg.payloadLength = strlen(msg.payload);

	nipc_send(&msg, fd);
	nipc_recv(&msg, fd);


	// nro sector; contenido
	conexiones->libre = FREE;

	buffer = strtok(msg.payload, "\0");
	buffer = strchr(buffer, ";") + 1;

	int i;


	return buffer;
}

void setSector(unsigned int sector, char *contenido){

	nipcMessage msg;
	int		 	fd,indice;

	indice = available_connection();

	fd = conexiones[indice].socket;

	 char string[intlen(sector)];
	 sprintf(string,"%d",10);
     strcat(string, ";");
     strcat(string,contenido);


	sprintf(msg.payloadID, "%d", fd);
	msg.type = '1';
	msg.payload = string;  // ESTE ES EL NRO DE SECTOR QUE QUIERO MODIFICAR, CON SU CONTENIDO
	msg.payloadLength = strlen(msg.payload);

	nipc_send(&msg, fd);
	nipc_recv(&msg, fd);


	// nro sector; contenido
	conexiones->libre = FREE;

}

int escribirSector(struct TDD_entry **nodoPath, unsigned int sector, char *buffer)
{
	nipcMessage 	msg;
	int 			indice;
	int				bytesEscritos;
	struct t_cache	*nodoCache;
	time_t 			*timeS = NULL;

	/* si no hay cache */
	if(*nodoPath != NULL)
	{
		nodoCache = check_cache(*nodoPath, sector);

		if(nodoCache != NULL)
		{
			pthread_mutex_lock(&(*nodoPath)->mutexCache);
			strcpy(nodoCache->buffer, buffer);
			time(timeS);
			nodoCache->timeStand = timeS;
			pthread_mutex_unlock(&(*nodoPath)->mutexCache);
		}else{
			pthread_mutex_lock(&(*nodoPath)->mutexCache);
			cargarCache(*nodoPath, buffer, sector);
			pthread_mutex_unlock(&(*nodoPath)->mutexCache);
		}

		bytesEscritos = strlen(buffer);
	}
	else
	{
		indice = available_connection();
		sprintf(msg.payloadID ,"%d", conexiones[indice].socket);
		msg.type = WRITE;
		armarPayload(msg, sector, buffer);
		bytesEscritos = nipc_send(&msg, atoi(msg.payloadID));
		nipc_recv(&msg, atoi(msg.payloadID));
		pthread_mutex_lock(&semConexiones);
		conexiones[indice].libre = FREE;
		pthread_mutex_unlock(&semConexiones);
		sem_post(&semSockContador);
	}

	return bytesEscritos;
}

void completarSector(unsigned int primerByteModificar, char **bufferCompletar, char *buf)
{
	char *buffer, *buffer2;

	strncpy(buffer, buf, primerByteModificar);
	strncpy(buffer2, *bufferCompletar, 512 - primerByteModificar);
	strcat(*bufferCompletar, buffer2);
	strcat(*bufferCompletar, buffer);
}

void escribirClustersRestantes(struct TDD_entry **nodoDelPath, char *buffer, unsigned int tamanioCluster, const char *path, unsigned int primerCluster, bootSector boot, int secPerBloque)
{
	div_t			division;
	int				k, clusterModificar;
	unsigned int	clustersPendientes, bloquesPendientes, primerSecClusterModif;
	unsigned int	clusterLibre, bytesEscritos;
	struct Nodo		*freeClusters;
	char			disk;

	division = div(strlen(buffer), tamanioCluster);

	if (division.rem != 0)
	{
		division.quot ++;
	}

	clustersPendientes = division.quot;
	bloquesPendientes = clustersPendientes * 2;

	for(k = 0; k < clustersPendientes; k++)
	{
		/* se hizo un analisis de por que paso primerCluster (complicado de explicar) */
		clusterModificar = clusterDeUnArchivo(disk, path, primerCluster + k);

		if(clusterModificar != -1)
		{
			primerSecClusterModif = getSectorClusterN(clusterModificar, boot);
		}
		else
		{
			freeClusters = getFreeClusters(disk);
			clusterLibre = unqueue(&freeClusters);
			primerSecClusterModif = getSectorClusterN(clusterLibre, boot);
			/* TODO enlazar el nuevo cluster al ultimo del arch */
		}

		if(bloquesPendientes == 1)
		{
			bytesEscritos = setBloque(primerSecClusterModif, 1 , secPerBloque, &buffer, nodoDelPath);

			bloquesPendientes =- secPerBloque;
		}
		else
		{
			bytesEscritos = setBloque(primerSecClusterModif, 1 , secPerBloque, &buffer, nodoDelPath);
			bytesEscritos =+ setBloque(primerSecClusterModif, 2 , secPerBloque, &buffer, nodoDelPath);

			bloquesPendientes =- secPerBloque * 2;
		}
	}
}
